M2.851 · Tipología y ciclo de vida de los datos · PRA2
2021-2 · Máster universitario en Ciencia de datos (Data science)
Estudios de Informática, Multimedia y Telecomunicación
El objetivo de esta actividad será el tratamiento de un dataset. En concreto, realizaremos un preprocesado y un análisis de datos del dataset tidy_dc_airbnb.csv (Kaggle, 2020).
En los últimos años, los alojamientos turísticos de particulares se han popularizado. La expansión de plataformas como Airbnb han tenido un impacto importante en la sociedad, por lo que resulta fundamental desarrollar nuevas herramientas que permitan comprender este nuevo mercado.
El presente proyecto tiene como objetivo el preprocesado y un primer análisis de un conjunto de datos de la plataforma Airbnb, referente a alojamientos situados en Washington DC. Nos hemos propuesto localizar los barrios más caros y más baratos, conocer cuáles son las variables que más impactan en el precio del alojamiento y desarrollar un modelo que permita predecir el precio de un nuevo alojamiento.
# Importamos las librerías necesarias
# Preprocesado
import numpy as np
import pandas as pd
import json
from shapely.geometry import shape, Point
# Visualización
import matplotlib.pyplot as plt
from matplotlib import colors
import seaborn as sns
import geopandas as gpd
import folium
# Análisis
from scipy import stats
from sklearn.impute import KNNImputer
from sklearn.linear_model import LinearRegression
# Cargamos los datos en un dataframe
df = pd.read_csv('data/tidy_dc_airbnb.csv')
print('El dataset cuenta con un total de {} columnas y de {} filas'
.format(df.shape[1], df.shape[0]))
El dataset cuenta con un total de 19 columnas y de 3671 filas
display(df.head())
| Unnamed: 0 | host_response_rate | host_acceptance_rate | host_listings_count | accommodates | room_type | bedrooms | bathrooms | beds | price | minimum_nights | maximum_nights | number_of_reviews | latitude | longitude | city | zipcode | state | tidy_price | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 92% | 91% | 26 | 4 | Entire home/apt | 1 | 1.0 | 2 | $160.00 | 1 | 1125 | 0 | 38.890046 | -77.002808 | Washington | 20003 | DC | 160 |
| 1 | 2 | 90% | 100% | 1 | 6 | Entire home/apt | 3 | 3.0 | 3 | $350.00 | 2 | 30 | 65 | 38.880413 | -76.990485 | Washington | 20003 | DC | 350 |
| 2 | 3 | 90% | 100% | 2 | 1 | Private room | 1 | 2.0 | 1 | $50.00 | 2 | 1125 | 1 | 38.955291 | -76.986006 | Hyattsville | 20782 | MD | 50 |
| 3 | 4 | 100% | NaN | 1 | 2 | Private room | 1 | 1.0 | 1 | $95.00 | 1 | 1125 | 0 | 38.872134 | -77.019639 | Washington | 20024 | DC | 95 |
| 4 | 5 | 92% | 67% | 1 | 4 | Entire home/apt | 1 | 1.0 | 1 | $50.00 | 7 | 1125 | 0 | 38.996382 | -77.041541 | Silver Spring | 20910 | MD | 50 |
La primera exploración del archivo elegido (tidy_dc_airbnb.csv) nos permite ver que cuenta con un total de 3671 filas y 19 columnas. Después de una primera exploración, se ha identificado el significado de cada columna:
Para realizar los análisis es necesario realizar las siguientes transformaciones preliminares:
# Realizamos las transformaciones indicadas en cada columna
df = df.rename(columns={"Unnamed: 0": "id"})
df['host_response_rate'] = df['host_response_rate'].str.rstrip('%').astype('float')
df['host_acceptance_rate'] = df['host_acceptance_rate'].str.rstrip('%').astype('float')
df['price'] = df['price'].str.replace(',','',regex=True).str.replace('$','',regex=True).astype(float)
df['coordenates'] = list(zip(df['longitude'],df['latitude']))
df = df[df['city'].str.contains('Washington')]
df = df.drop(['zipcode', 'state', 'tidy_price'], axis=1)
Además, enriqueceremos el dataset con una nueva columna neighborhood. Esta columna contendrá el barrio en el que se ubica la vivienda. Para ello, utilizaremos un geojson que contiene las coordenadas de los barrios de la ciudad de Washington. Dicho geojson puede descargarse en la siguiente dirección: https://opendata.dc.gov/datasets/neighborhood-clusters/explore?location=38.381222%2C-77.277478%2C8.82
# Definimos una función que devuelve el barrio de Washington DC en el que se encuentra una coordenada
def get_nbh(point, js):
for feature in js['features']:
polygon = shape(feature['geometry'])
if polygon.contains(point):
return(feature["properties"]["NBH_NAMES"])
# Cargamos el geojson
with open('data/Neighborhood_Clusters.geojson') as f:
js = json.load(f)
# Comprobamos a qué barrio pertenece cada vivienda
neighborhood = []
for point in df.coordenates:
point = Point(point[0], point[1])
neighborhood.append(get_nbh(point, js))
# Añadimos una nueva columna al dataframe
df["neighborhood"] = neighborhood
Conociendo las coordenadas de cada vivienda podemos visualizar el conjunto de viviendas en el mapa de la ciudad y, de este modo, detectar visualmente los barrios caros y baratos. Para ello, hemos pasado los precios a escala logarítmica, que refleja mejor la naturaleza dicotómica de la clasificación. El objetivo de este gráfico es generar una nueva columna expensive_neighborhood a partir de la información visual que identifiquemos. La idoneidad de dicha columna será contrastada en el ejercicio 4.
gdf = gpd.read_file('data/Neighborhood_Clusters.geojson')
gdf_houses = gpd.GeoDataFrame(
df, geometry=gpd.points_from_xy(df.longitude, df.latitude))
gdf_houses["price_log"] = np.log10(gdf_houses["price"])
m = gdf.explore(tooltip="NBH_NAMES", color="white")
gdf_houses.explore(m=m,
tooltip="neighborhood",
column="price_log",
color="price_log",
cmap="seismic",
legend=False,
marker_kwds=dict(radius=1, fill=True))
folium.TileLayer('Stamen Toner', control=True).add_to(m) # use folium to add alternative tiles
folium.LayerControl().add_to(m) # use folium to add layer control
display(m)
Como vemos en el gráfico, hay algunos barrios que presentan una alta concentración de puntos rojos (podemos pasar el ratón por encima del barrio o de la vivienda para identificar el barrio), como:
Estos serán los barrios que identifiquemos como caros, para lo que creremos una nueva variable.
expensive_nbh = ['Capitol Hill, Lincoln Park',
'Georgetown, Burleith/Hillandale',
'West End, Foggy Bottom, GWU',
'Downtown, Chinatown, Penn Quarters, Mount Vernon Square, North Capitol Street']
df['expensive_nbh'] = df.neighborhood.apply(lambda x: 1 if x in expensive_nbh else 0)
Por último eliminamos las columnas empleadas para obtener la variable expensive_nbh:
# Eliminamos columnas innecesarias
df = df.drop(columns=['latitude',
'longitude',
'coordenates',
'city',
'coordenates',
'id',
'neighborhood',
'geometry',
'price_log'], axis=1)
Mostramos en pantalla la dimensión final del dataset y los primeros registros:
print('El dataset cuenta con un total de columnas {} y de {} filas'
.format(df.shape[1],df.shape[0]))
El dataset cuenta con un total de columnas 13 y de 3644 filas
display(df.head())
| host_response_rate | host_acceptance_rate | host_listings_count | accommodates | room_type | bedrooms | bathrooms | beds | price | minimum_nights | maximum_nights | number_of_reviews | expensive_nbh | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 92.0 | 91.0 | 26 | 4 | Entire home/apt | 1 | 1.0 | 2 | 160.0 | 1 | 1125 | 0 | 1 |
| 1 | 90.0 | 100.0 | 1 | 6 | Entire home/apt | 3 | 3.0 | 3 | 350.0 | 2 | 30 | 65 | 1 |
| 3 | 100.0 | NaN | 1 | 2 | Private room | 1 | 1.0 | 1 | 95.0 | 1 | 1125 | 0 | 0 |
| 5 | 100.0 | 100.0 | 1 | 4 | Entire home/apt | 2 | 1.0 | 4 | 99.0 | 1 | 1125 | 0 | 0 |
| 6 | 100.0 | NaN | 1 | 4 | Entire home/apt | 2 | 2.0 | 2 | 100.0 | 3 | 1125 | 0 | 0 |
Consultamos los elementos nulos y vemos que únicamente tienen valores perdidos las variables host_acceptance_rate (16.30%) y host_response_rate (11.47%). En este caso, no consideramos los valores igual a 0 como nulos ya que pueden tener un significado en la realidad; como apartamentos que no han recibido ninguna valoración, apartamentos que no tienen cama porque tienen otra alternativa (e.g., sofá), etc.
La imputación de valores perdidos es un tema controvertido ya que no hay una única recomendación sobre el porcentage máximo a partir del cuál no se deben imputar valores perdidos. No obstante, existe cierto consenso en que se puede realizar la imputación si éstos no representan más del 20% de los registros. En este caso, por tanto, imputaremos los valores en ambas variables siguiendo el método kNN. No obstante, realizaremos dicha transformación tras tratar los valores atípicos y extremos.
# Consulta de elementos nulos (expresado en porcentaje)
display(pd.DataFrame(df.isnull().sum() / len(df)*100).rename({0:"Nulos"}, axis=1))
| Nulos | |
|---|---|
| host_response_rate | 11.470911 |
| host_acceptance_rate | 16.300768 |
| host_listings_count | 0.000000 |
| accommodates | 0.000000 |
| room_type | 0.000000 |
| bedrooms | 0.000000 |
| bathrooms | 0.000000 |
| beds | 0.000000 |
| price | 0.000000 |
| minimum_nights | 0.000000 |
| maximum_nights | 0.000000 |
| number_of_reviews | 0.000000 |
| expensive_nbh | 0.000000 |
Para detectar los valores atípicos aplicamos la función describe mediante la cual podemos ver los principales estatísticos descriptivos de las variables cuantitativas. Esta función es muy útil ya que nos permite identificar posibles errores como porcentajes superiores a 100 o precios negativos.
# Exploración de las distribuciones de las variables cuantitativas
display(df.describe())
| host_response_rate | host_acceptance_rate | host_listings_count | accommodates | bedrooms | bathrooms | beds | price | minimum_nights | maximum_nights | number_of_reviews | expensive_nbh | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 3226.000000 | 3050.000000 | 3644.000000 | 3644.000000 | 3644.000000 | 3644.000000 | 3644.000000 | 3644.000000 | 3644.000000 | 3.644000e+03 | 3644.000000 | 3644.00000 |
| mean | 91.400806 | 86.376393 | 12.899286 | 3.197859 | 1.209111 | 1.258095 | 1.648738 | 149.148463 | 2.233260 | 5.928732e+05 | 15.170692 | 0.19978 |
| std | 14.886035 | 21.264548 | 62.394902 | 2.006053 | 0.841332 | 0.587408 | 1.186481 | 137.864318 | 3.623274 | 3.557498e+07 | 29.307778 | 0.39989 |
| min | 7.000000 | 0.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 1.000000 | 10.000000 | 1.000000 | 1.000000e+00 | 0.000000 | 0.00000 |
| 25% | 90.000000 | 79.000000 | 1.000000 | 2.000000 | 1.000000 | 1.000000 | 1.000000 | 85.000000 | 1.000000 | 1.097500e+02 | 1.000000 | 0.00000 |
| 50% | 100.000000 | 98.000000 | 1.000000 | 2.000000 | 1.000000 | 1.000000 | 1.000000 | 115.000000 | 2.000000 | 1.125000e+03 | 4.000000 | 0.00000 |
| 75% | 100.000000 | 100.000000 | 3.000000 | 4.000000 | 1.000000 | 1.000000 | 2.000000 | 165.000000 | 3.000000 | 1.125000e+03 | 16.000000 | 0.00000 |
| max | 100.000000 | 100.000000 | 480.000000 | 16.000000 | 10.000000 | 8.000000 | 16.000000 | 2822.000000 | 180.000000 | 2.147484e+09 | 362.000000 | 1.00000 |
En la exploración, observamos anomalías en las siguientes variables:
Visualizamos también las frecuencias en la variable categórica room type, para ver si hay algún valor que sea necesario integrar (por ejemplo dos cadenas aludiendo a un mismo concepto):
# Exploración de las distribuciones de la variable categórica
display(pd.DataFrame(df['room_type'].value_counts()))
| room_type | |
|---|---|
| Entire home/apt | 2366 |
| Private room | 1183 |
| Shared room | 95 |
No observamos ningún valor atípico ni transformación necesaria.
Respecto del resto de variables, aunque puedan presentar outliers, estas (posibles) anomalías podrían representar valores, si bien extremos, reales. No obstante, siguiendo una de las recomendaciones más extendidas, consideraremos outliers a eliminar aquellos registros que se alejen más de tres desviaciones típicas de la media en las variables: price, minimum_nights, maximum_nights y number_of_reviews.
Nota: Nótese que, mediante la operación de borrado, se eliminan las anomalías ya identificadas en la columna maximum_nights.
# Tratamiento de los valores atípicos y extremos
cols = ['price','minimum_nights',
'maximum_nights','number_of_reviews']
def del_outliers(df, cols, std=3):
for c in cols:
lim_inf = df[c].mean()-3*df[c].std()
lim_sup = df[c].mean()+3*df[c].std()
df = df[df[c].between(lim_inf, lim_sup)]
return df
df = del_outliers(df, cols)
Mostramos el número de registros resultantes tras todas las transformaciones:
print('El dataset cuenta con un total de columnas {} y de {} filas'
.format(df.shape[1],df.shape[0]))
El dataset cuenta con un total de columnas 13 y de 3584 filas
Ahora ya podemos imputar los valores perdidos mediante el método de los k vecinos más cercanos:
# Imputación de valores perdidos
imputer = KNNImputer()
df_knn = df.drop(['room_type'], axis=1)
df_knn = pd.DataFrame(imputer.fit_transform(df_knn))
df['host_response_rate'] = df_knn[0].tolist()
df['host_acceptance_rate'] = df_knn[1].tolist()
display(pd.DataFrame(df.isnull().sum()).rename({0:"Nulos"}, axis=1))
| Nulos | |
|---|---|
| host_response_rate | 0 |
| host_acceptance_rate | 0 |
| host_listings_count | 0 |
| accommodates | 0 |
| room_type | 0 |
| bedrooms | 0 |
| bathrooms | 0 |
| beds | 0 |
| price | 0 |
| minimum_nights | 0 |
| maximum_nights | 0 |
| number_of_reviews | 0 |
| expensive_nbh | 0 |
En el segundo apartado de la actividad, hemos visualizado la distribución de viviendas por barrios de la ciudad de Washington y hemos identificado visualmente aquellos barrios que, aparentemente, contenían precios por noche más altos. Hemos denominado barrios caros a dichos barrios, y hemos añadido esta información a nuestro dataset. No obstante, es muy conveniente comprobar si la identificación realizada es o no correcta y si, por lo tanto, podemos afirmar que los precios son, por norma general, más altos en los barrios que hemos identificado como caros. Para ello, primero separaremos los precios en dos conjuntos: precios de barrios baratos y precios de barrios caros.
precios_barrios_baratos = df[df.expensive_nbh==0].price
precios_barrios_caros = df[df.expensive_nbh==1].price
En función del contraste de hipótesis que deseemos efectuar, utilizaremos uno u otro estadístico de contraste (t test, Welch t test, prueba chi cuadrado...). Algunas de estas pruebas son paramétricas, por lo que conviene conocer la distribución de los datos y si puede asumirse que las muestras a comparar comparten o no la misma varianza.
Comenzamos visualizando los gráficos QQ de cada muestra:
fig, axes = plt.subplots(1, 2)
fig.set_size_inches(10, 5)
stats.probplot(precios_barrios_baratos, dist="norm", plot=axes[0])
axes[0].axis('off')
axes[0].set_title("QQ plot Precios Barrios Baratos")
stats.probplot(precios_barrios_caros, dist="norm", plot=axes[1])
axes[1].axis('off')
axes[1].set_title("QQ plot Precios Barrios Caros")
plt.show()
Como vemos, no parece en absoluto que los datos estén distribuidos normalmente. En cualquier caso, realizaremos el test de Shapiro-Wilk:
def is_normal(v):
stat, p = stats.shapiro(v)
print("Estadístico: {}\nP valor:{}".format(round(stat, 2), round(p, 2)))
if p > 0.05:
print("Probablemente normal")
else:
print("Probablemente no normal")
print("Distribución precios barrios baratos:")
is_normal(precios_barrios_baratos)
print("\nDistribución precios barrios caros:")
is_normal(precios_barrios_caros)
Distribución precios barrios baratos: Estadístico: 0.8 P valor:0.0 Probablemente no normal Distribución precios barrios caros: Estadístico: 0.88 P valor:0.0 Probablemente no normal
Habida cuenta de los resultados, concluimos que los datos no se distribuyen normalmente. Para determinar la homocedasticidad o heterocedasticidad de los datos utilizaremos el test de Levene:
def same_variance(x1, x2):
stat, p = stats.levene(x1, x2)
print("Estadístico: {}\nP valor:{}".format(round(stat, 2), round(p, 2)))
if p > 0.05:
print("Probablemente misma varianza")
else:
print("Probablemente distinta varianza")
same_variance(precios_barrios_baratos, precios_barrios_caros)
Estadístico: 51.0 P valor:0.0 Probablemente distinta varianza
Del test anterior inferimos que los datos tienen distinta varianza.
Nos interesa determinar si el precio medio por noche en los barrios que creemos que son caros es realmente superior al precio medio por noche en los barrios que hemos identificado como baratos. La hipótesis nula es el precio medio por noche es igual en los barrios identificados como baratos que en los identificados como caros, mientras que la hipótesis alternativa es el precio medio por noche es inferior en los barrios identificados como baratos que en los identificados como caros. Tenemos, por lo tanto, el siguiente contraste de hipótesis:
$H_0 = \mu_{baratos} = \mu_{caros}$
$H_1 = \mu_{baratos} < \mu_{caros}$
Tenemos, por lo tanto, que contrastar las medias de dos muestras que no se distribuyen normalmente y que no comparten la misma varianza. Ahora bien, respecto de la primera consideración (distribución no normal), quizás podamos aplicar el teorema del límite central en caso de que contemos con un número suficientemente alto de observaciones. Comprobemos, pues, este extremo:
print("Observaciones barrios baratos: {}".format(len(precios_barrios_baratos)))
print("Observaciones barrios caros: {}".format(len(precios_barrios_caros)))
Observaciones barrios baratos: 2879 Observaciones barrios caros: 705
En los dos casos tenemos un elevado número de observaciones, en cualquier caso más de treinta, por lo que podemos aplicar el teorema del límite central, según el cual la distribución de las medias muestrales de una población se distribuye normalmente cuando tenemos un número lo suficientemente elevado de observaciones (más de treinta, en la práctica). Dicha distribución de las medias muestrales tiene media igual a la poblacional y desviación típica $\sigma/\sqrt(n)$.
Por lo tanto, en virtud de este teorema, tenemos dos distribuciones normales (las distribuciones de las medias muestrales de cada conjunto de precios) con media $\mu_{baratos}$ y $\mu_{caros}$; y con desviación típica $\sigma_{baratos}/\sqrt(n_{baratos})$ y $\sigma_{caros}/\sqrt(n_{caros})$, respectivamente.
En consecuencia, podemos aplicar la prueba t test de Welch, una prueba no paramética que se utiliza para determinar si dos conjuntos de datos que se distribuyen normalmente tienen distinta media. A diferencia de la prueba t tradicional, la variante de Welch no asume la homocedasticidad de los datos, por lo que podemos aplicarlo perfectamente.
def welch_test(x1, x2, alt):
stat, p = stats.ttest_ind(x1, x2, equal_var = False, alternative=alt)
print("Estadístico: {}\nP valor:{}".format(round(stat, 2), round(p, 2)))
if p > 0.05:
print("Se acepta la hipótesis nula")
else:
print("Se rechaza la hipótesis nula")
welch_test(precios_barrios_baratos, precios_barrios_caros, "less")
Estadístico: -11.93 P valor:0.0 Se rechaza la hipótesis nula
Para la prueba t de Welch hemos obtenido un p valor de 0, por lo que para cualquier nivel de significancia podemos rechazar la hipótesis nula, lo que implica que, efectivamente, el precio medio es mayor en los barrios que hemos identificado visualmente como caros, por lo que la introducción de la variable expensive_nbh está plenamente justificada.
Para determinar qué variables predictoras están más fuertemente correlacionadas con la variable respuesta, podemos calcular el coeficiente de correlación lineal de pearson entre cada predictor y la respuesta.
# Separamos la variable respuesta de las variables predictoras
X = df.drop(['price'],axis=1)
y = df['price']
# Aplicamos one hot encoding a las variables categóricas
X = pd.get_dummies(data=X, columns=['room_type'])
display(pd.DataFrame(X.apply(lambda x: x.corr(y, method="pearson"))).rename({0: "Coef Pearson"}, axis=1).sort_values(
by="Coef Pearson", key=abs, ascending=False))
| Coef Pearson | |
|---|---|
| accommodates | 0.586298 |
| bedrooms | 0.569235 |
| beds | 0.486227 |
| bathrooms | 0.485796 |
| room_type_Entire home/apt | 0.436862 |
| room_type_Private room | -0.397048 |
| host_listings_count | 0.243117 |
| expensive_nbh | 0.219341 |
| room_type_Shared room | -0.140928 |
| number_of_reviews | -0.075510 |
| host_acceptance_rate | -0.065567 |
| host_response_rate | -0.030606 |
| maximum_nights | 0.028090 |
| minimum_nights | 0.025945 |
Vemos que, según el coeficiente de correlación de Pearson, las variables más relacionadas con el precio son la capacidad del alojamiento, el número de huéspedes, el número de habitaciones, el número de camas, el número de baños, el tipo de alojamiento (vivienda, habitación...) y el tipo de barrio (caro o barato). Podemos graficar la correlación lineal entre las variables numéricas, ordinales o continuas, y la variable respuesta mediante diagramas de puntos:
fig, axes = plt.subplots(3, 3)
fig.set_size_inches(15, 15)
cols = df.drop(["price", "room_type", "expensive_nbh"], axis=1).columns
v = 0
for i in list(range(0, 3)):
for j in list(range(0, 3)):
var = cols[v]
sns.regplot(x=var, y="price", data=df, color='navajowhite',
ax=axes[i,j]).set(title='Correlación lineal {}-price'.format(var))
v+=1
axes[i, j].axis('off')
plt.show()
Asimismo, y con el fin de detectar la colinealidad, en caso de haberla, calculamos la correlación lineal entre las variables numéricas predictoras, para lo que generamos una matriz de correlación:
corr = df.drop(["price", "room_type", "expensive_nbh"], axis=1).corr()
mask = np.triu(np.ones_like(corr, dtype=bool))
f, ax = plt.subplots(figsize=(11, 9))
cmap = sns.diverging_palette(230, 20, as_cmap=True)
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3, center=0, cbar=False,
square=True, linewidths=.5, cbar_kws={"shrink": .5}, annot=True)
plt.show()
Vemos que la variable accommodates está fuertemente correlacionada con beds. Dado que dicha correlación es superior a 0.8, podemos eliminar una de las dos variables antes de implementar la regresión lineal múltiple. Dado que accommodates está más correlacionada con price que beds, eliminamos esta última.
X = X.drop("beds", axis=1)
Por último, definiremos a partir de los datos un modelo de regresión lineal múltiple que nos permita predecir futuros precios por noche.
reg = LinearRegression().fit(X, y)
print("El coeficiente de determinación del modelo es: {}".format(round(reg.score(X, y), 2)))
El coeficiente de determinación del modelo es: 0.54
El coeficiente de determinación es de 0.54, lo que implica que el modelo solo explica algo más de la mitad del 50% de la varianza de los datos. Si solo atendemos a este indicador, podemos concluir que el modelo es moderadamente predictivo, aunque también podríamos optar por otras métricas quizás más esclarecedoras, como el error estándar del modelo.
# Aislamos y mostramos los coeficientes del hiperplano
intercept = round(reg.intercept_, 2)
coefs = dict(zip(X.columns, np.round_(reg.coef_, 1)))
print("El valor del intercepto es: {}".format(intercept))
print("El valor de los coeficientes es: {}".format(coefs))
El valor del intercepto es: 22.63
El valor de los coeficientes es: {'host_response_rate': 0.0, 'host_acceptance_rate': -0.1, 'host_listings_count': 0.2, 'accommodates': 8.3, 'bedrooms': 28.4, 'bathrooms': 27.8, 'minimum_nights': -0.5, 'maximum_nights': 0.0, 'number_of_reviews': -0.1, 'expensive_nbh': 28.9, 'room_type_Entire home/apt': 37.0, 'room_type_Private room': -5.2, 'room_type_Shared room': -31.8}
Por lo tanto, demos estimar el precio por noche a partir del siguiente modelo
$\hat{y} = 22.78 + 0x_{resp rate} - 0.1x_{accept rate} + 0.2x_{list count} + 9.3x_{accom} + 28.9x_{bedrooms} + 28x_{bathrooms} - 0.5x_{minnights} - 0x_{maxnights} -0.1x_{revisions} +28.9x_{expensivenbh} + 36.5x_{entire} -5.7x_{privaterrom} -30.8x_{privateroom}$
Finalmente, exportamos el dataframe resultante en un nuevo csv:
df.to_csv("data/airbnb_final.csv")
A partir de los resultados obtenidos, podemos extraer las siguientes conclusiones: